/*
   Copyright (C) 2009 Red Hat, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

#ifndef REDS_H_
#define REDS_H_

#include <stdint.h>
#include <sys/uio.h>
#include <spice/vd_agent.h>
#include <common/marshaller.h>
#include <common/messages.h>
#include <sys/queue.h>

#include "char-device.h"
#include "spice.h"
#include "red-channel.h"
#include "main-dispatcher.h"
#include "migration-protocol.h"

static inline QXLInterface * qxl_get_interface(QXLInstance *qxl)
{
    return SPICE_CONTAINEROF(qxl->base.sif, QXLInterface, base);
}

/* main thread only */
void reds_handle_channel_event(RedsState *reds, int event, SpiceChannelEventInfo *info);

void reds_disable_mm_time(RedsState *reds);
void reds_enable_mm_time(RedsState *reds);
uint32_t reds_get_mm_time(void);
void reds_register_channel(RedsState *reds, RedChannel *channel);
void reds_unregister_channel(RedsState *reds, RedChannel *channel);
RedChannel *reds_find_channel(RedsState *reds, uint32_t type, uint32_t id);
int reds_get_free_channel_id(RedsState *reds, uint32_t type);
SpiceMouseMode reds_get_mouse_mode(RedsState *reds); // used by inputs_channel
gboolean reds_config_get_agent_mouse(const RedsState *reds); // used by inputs_channel
int reds_has_vdagent(RedsState *reds); // used by inputs channel
bool reds_config_get_playback_compression(RedsState *reds); // used by playback channel

void reds_handle_agent_mouse_event(RedsState *reds, const VDAgentMouseState *mouse_state); // used by inputs_channel

GArray* reds_get_renderers(RedsState *reds);

enum {
    RED_RENDERER_INVALID,
    RED_RENDERER_SW,

    RED_RENDERER_LAST
};

// Temporary measures to make splitting reds.c to inputs-channel.c easier

/* should be called only from main_dispatcher */
void reds_client_disconnect(RedsState *reds, RedClient *client);

// Temporary (?) for splitting main channel
void reds_marshall_migrate_data(RedsState *reds, SpiceMarshaller *m);
SpiceMsgChannels *reds_msg_channels_new(RedsState *reds);

/* callbacks from main channel messages */

void reds_on_main_agent_start(RedsState *reds, MainChannelClient *mcc, uint32_t num_tokens);
void reds_on_main_agent_tokens(RedsState *reds, MainChannelClient *mcc, uint32_t num_tokens);
uint8_t *reds_get_agent_data_buffer(RedsState *reds, MainChannelClient *mcc, size_t size);
void reds_release_agent_data_buffer(RedsState *reds, uint8_t *buf);
void reds_on_main_agent_data(RedsState *reds, MainChannelClient *mcc, const void *message,
                             size_t size);
void reds_on_main_migrate_connected(RedsState *reds, int seamless); //should be called when all the clients
                                                   // are connected to the target
bool reds_handle_migrate_data(RedsState *recs, MainChannelClient *mcc,
                              SpiceMigrateDataMain *mig_data, uint32_t size);
void reds_on_main_mouse_mode_request(RedsState *reds, void *message, size_t size);
/* migration dest side: returns whether it can support seamless migration
 * with the given src migration protocol version */
int reds_on_migrate_dst_set_seamless(RedsState *reds, MainChannelClient *mcc, uint32_t src_version);
void reds_on_client_semi_seamless_migrate_complete(RedsState *reds, RedClient *client);
void reds_on_client_seamless_migrate_complete(RedsState *reds, RedClient *client);
void reds_on_main_channel_migrate(RedsState *reds, MainChannelClient *mcc);

void reds_set_client_mm_time_latency(RedsState *reds, RedClient *client, uint32_t latency);
uint32_t reds_get_streaming_video(const RedsState *reds);
GArray* reds_get_video_codecs(const RedsState *reds);
spice_wan_compression_t reds_get_jpeg_state(const RedsState *reds);
spice_wan_compression_t reds_get_zlib_glz_state(const RedsState *reds);
SpiceCoreInterfaceInternal* reds_get_core_interface(RedsState *reds);
void reds_update_client_mouse_allowed(RedsState *reds);
MainDispatcher* reds_get_main_dispatcher(RedsState *reds);

/* Get the recording object stored in RedsState.
 * You should free with red_record_unref.
 */
struct RedRecord *reds_get_record(RedsState *reds);

/* fd watches/timers */
SpiceWatch *reds_core_watch_add(RedsState *reds,
                                int fd, int event_mask,
                                SpiceWatchFunc func,
                                void *opaque);
void reds_core_watch_update_mask(RedsState *reds,
                                 SpiceWatch *watch,
                                 int event_mask);
void reds_core_watch_remove(RedsState *reds, SpiceWatch *watch);

SpiceTimer *reds_core_timer_add(RedsState *reds,
                                SpiceTimerFunc func,
                                void *opaque);
void reds_core_timer_start(RedsState *reds,
                           SpiceTimer *timer,
                           uint32_t ms);
void reds_core_timer_cancel(RedsState *reds,
                            SpiceTimer *timer);
void reds_core_timer_remove(RedsState *reds,
                            SpiceTimer *timer);

#define GPU_MAX_HEIGHT 4096
#define GPU_MAX_WIDTH 4096
#define CURSORSIZE 256
#define GPU_MAX_MONITORS 4
#define CURSOR_WIDTH 256
#define CURSOR_HEIGHT 256
#define GPU_GROUP_ID_EX 0xFEFE
#define GPU_MIXTURE_MIN 0
#define GPU_PORT_BUFFER 1024

typedef enum {
    GPU_GROUP_TYPE_IMAE,
    GPU_GROUP_TYPE_CURSOR,
    GPU_GROUP_TYPE_STREAM,
    GPU_GROUP_TYPE_END
}GpuGroupType;

typedef enum {
    GPU_MIXTURE_START,
    GPU_MIXTURE_STOP,
    GPU_MIXTURE_MARK,
}GpuMixtureState;

typedef enum {
    GPU_MODE_SYS,
    GPU_MODE_GSTREAM,
    GPU_MODE_STREAM,
}GpuModeType;

enum {
    PERF_COM_FROM_GUEST,
    PERF_COM_FROM_SERVER,
    PERF_COM_FROM_CLIENT
};

typedef enum {
    GPU_STREAM_INIT,
    GPU_STREAM_CREATE,
    GPU_STREAM_READY,
}GpuStreamState;

typedef enum {
    GPU_PREP_STREAM_INIT,       // the default value
    GPU_PREP_STREAM_DROP,       // need drop current stream id
}GpuPrepStreamState;

// guest to server
typedef enum {
    GPU_CCOMMAND_FULL_FRAME      = 0xFFFF,   // departed
    GPU_CCOMMAND_GPU_GROUP_ID    = 0xFFFE,   // GPU_GROUP_ID_EX, rever field
    GPU_CCOMMAND_CURSOR          = 0xFFFD,   // cursor
    GPU_CCOMMAND_STREAM          = 0xFFFC,   // stream
    GPU_CCOMMAND_CLIENT          = 0xFFFB,   // query client connected
    GPU_CCOMMAND_RESOLVE         = 0xFFFA,   // resolve
    GPU_CCOMMAND_MIXSTREAM       = 0xFFF9,   // mixture stream
    GPU_CCOMMAND_GPU_MODE        = 0xFFF8,   // set gpu mode
    GPU_CCOMMAND_PERF_SYS        = 0xFFF7,   // sys mode perf code
    GPU_CCOMMAND_PERF_STREAM     = 0xFFF6,   // query stream id created
    GPU_CCOMMAND_NOUSE           = 0xFFF5,   // rever field
    GPU_CCOMMAND_HEART           = 0xFFF4,   // heart data, trigger disconnect, stream ready
    GPU_CCOMMAND_GCHANNEL_CREATE = 0xFFF3,   // request create gpuchannel
    GPU_CCOMMAND_GCHANNEL_FORMAT = 0xFFF2,   // request create gpuchannel stream id
    GPU_CCOMMAND_GCHANNEL_DATA   = 0xFFF1,   // stream data
    GPU_CCOMMAND_GCHANNEL_CURSOR = 0xFFF0,   // stream cursor data
    GPU_CCOMMAND_DATA            = 0x0000,   // 0x0000 - 0x1024, sys mode rectangle change data
}GpuCCommand;

// server to guest
typedef enum {
    GPU_COMMAND_ACK_CLIENT          = 0x0001,   // ack client state
    GPU_COMMAND_ACK_RESOLVE         = 0x0002,   // ack resolve state
    GPU_COMMAND_DROP_STREAM         = 0x0003,   // report drop stream
    GPU_COMMAND_ACK_SYS             = 0x0004,   // report sys push frame number
    GPU_COMMAND_ACK_PERF            = 0x0005,   // report perf information
    GPU_COMMAND_ACK_STREAM_ENABLE   = 0x0006,   // report stream id created
    GPU_COMMAND_ACK_GCHANNEL_STATE      = 0x0007, // report channel connected/disconnected
}GpuCommand;

typedef enum {
    GPU_MAIN_CHANNEL_CONNECTED,
    GPU_MAIN_CHANNEL_DISCONNECTED,
}GpuMainChannelDisconnected;

typedef enum {
    GPU_RESOLVE_INIT = -1,
    GPU_RESOLVE_DESTROY_SURFACE = 0,
    GPU_RESOLVE_CREATE_SURFACE = 1,
    GPU_RESOLVE_FINISH_SURFACE = 2,
}GpuResolveState;

typedef struct _perf_com{
    int from_id;
}perf_com;

typedef enum {
    GPU_IMAGE_TYPE_HEART,
    GPU_IMAGE_TYPE_FRAME,
    GPU_IMAGE_TYPE_RECT
}GpuImageType;

enum{
    SERVER_PERF_RECIVER_DATA,
    SERVER_PERF_SEND_DATA,
};

typedef struct _server_perf{
    perf_com from;
    uint32_t frames;
    uint32_t mm_time;
    int type;
}server_perf;

struct gpu_stream_item
{
    uint8_t *data;
    uint32_t size;
    uint32_t frame_mm_time;
    int mon_id;
    TAILQ_ENTRY(gpu_stream_item) tailq;
};

TAILQ_HEAD(g_gpu_streams, gpu_stream_item);

typedef struct _gpu_rect{
    int32_t top;
    int32_t left;
    int32_t bottom;
    int32_t right;
    int32_t len;
    int begin;
    int end;
    int mon_id;
    uint8_t *bitmap;
}gpu_rect;

struct gpu_image_item
{
    int index;
    int mon_id;
    GpuGroupType type;
    GpuImageType image_type;
    gpu_rect data;
    uint32_t frame_mm_time;
    uint32_t last_rect;
    uint32_t frames;
    TAILQ_ENTRY(gpu_image_item) tailq;
};

TAILQ_HEAD(g_gpu_images, gpu_image_item);

typedef struct _cursorData {
    int len;
    int w;
    int h;
    int hide;
    int type;
    int x;
    int y;
    unsigned char data[CURSORSIZE * CURSORSIZE * 4];
}cursorData;

typedef struct _RectChangeMixtureEx {
    int index;
    GpuMixtureState type;
    int mon_id;
    uint16_t left, top, right, bottom;
    int dataLen;
    int count;
    uint8_t data[0];
}RectChangeMixtureEx;

typedef struct _RectChangeGpuModeEx{
    int index;
    int mode;
}RectChangeGpuModeEx;

typedef struct _GchannelCreate {
    int cmd;            // GPU_CCOMMAND_GCHANNEL_CREATE
    int size;           // except cmd, size field, other data size
    int monitor_id;
    int reserve;        // must be 0
}GchannelCreate;

typedef struct _GchannelFormat {
    int cmd;            // GPU_CCOMMAND_GCHANNEL_FORMAT
    int size;           // except cmd, size field, other data size
    int monitor_id;
    int32_t width;
    int32_t height;
    int codes;          // must be 3
}GchannelFormat;

typedef struct _GchannelData {
    int cmd;            // GPU_CCOMMAND_GCHANNEL_FORMAT
    int size;           // except cmd, size field, other data size
    int monitor_id;
    int datalen;        // data size
    uint8_t data[0];
}GchannelData;

typedef struct _gpu_monitor_ext{
    QXLInstance *g_qxl;
    uint8_t *g_primary_surface;
    int g_mon_id;
    int g_width;
    int g_height;
    int g_pitch;
    int g_show_cursor;
    int g_stream_item_couter;
    int g_stream_created_couter;
    int g_release;
    int g_stream_frames;            // stream frame number
    int is_first_frame;
    uint32_t g_last_frame_mm_time;
    uint32_t m_sys_frames;          // respon guest the dcc send frames
    uint32_t m_debug_local_frame_time;
    cursorData g_cursorData;
    struct g_gpu_streams g_streams;
    struct g_gpu_images g_images;
    RectChangeMixtureEx g_mix;
    GpuStreamState m_stream_state;
    uint8_t *g_mix_mark_data;
    int g_mix_mark_data_len;
    pthread_mutex_t g_lock;
    pthread_mutex_t g_stream_lock;
    void *m_gpunvidia_channel;
    void *m_gpunvidia_cursor_channel;
    int m_statistic_image_item_malloc;
    int m_statistic_image_item_free;
    int m_statistic_image_bitmap_malloc;
    int m_statistic_image_bitmap_free;
    int m_statistic_image_queue_free;
}gpu_monitor_ext;

#define STREAM_DROP_FLAG 1
typedef struct _gpu_ext{
    void *m_charDev;
    int g_trigger;
    GpuResolveState m_resolve_state;
    int (*channel_is_connected)(int);
    int g_start_stream;
    int m_is_gpu_stream_mode;
    GpuPrepStreamState m_prep_stream_state;
    GpuMainChannelDisconnected m_main_disconnected;
    GpuModeType m_mode_type;
    uint32_t uid;
    uint8_t *g_uncompress_data;
    uint8_t g_stream_drop_flags;
    gpu_monitor_ext *g_mon_ext[GPU_MAX_MONITORS];
    int m_overstock;
}gpu_ext;

extern gpu_ext *g_gpu_ext;

static inline gpu_monitor_ext *reds_gpu_get_ext(uint32_t id)
{
    if (id >= GPU_MAX_MONITORS || !g_gpu_ext) return NULL;
    return g_gpu_ext->g_mon_ext[id];
}

static inline QXLInstance *reds_gpu_get_qxl(uint32_t id)
{
    if (id >= GPU_MAX_MONITORS || !g_gpu_ext) return NULL;
    return g_gpu_ext->g_mon_ext[id]->g_qxl;
}

static inline gboolean reds_gpu_trigger(void)
{
    return g_gpu_ext && g_gpu_ext->g_trigger == 1;
}

static inline gboolean reds_is_stream_mode(void)
{
    return g_gpu_ext && g_gpu_ext->m_is_gpu_stream_mode == 1;
}

static inline void reds_sync_reset(int i)
{
    spice_warn_if_fail(i < GPU_MAX_MONITORS);

    if(g_gpu_ext) {
        __sync_lock_test_and_set(&g_gpu_ext->g_mon_ext[i]->m_sys_frames, 0);
        __sync_lock_release(&g_gpu_ext->g_mon_ext[i]->m_sys_frames);
    }
}

static inline void reds_sync_reset_all()
{
    reds_sync_reset(0);
    reds_sync_reset(1);
    reds_sync_reset(2);
    reds_sync_reset(3);
}

extern gpu_monitor_ext *reds_gpu_get_monitor_ext(gpointer opaque);
extern gpu_monitor_ext *reds_gpu_get_cursor_monitor_ext(gpointer opaque);
extern int reds_gpu_get_drawable_type(gpointer opaque);
extern int reds_gpu_is_last_rect(gpointer opaque, int *frames);
extern void reds_perf_send(int frames, uint32_t mm_time, int type);
extern int reds_gpu_enable(void);

#endif /* REDS_H_ */
